########################################################################
# Copyright 2008 Cory James Tobin
#
# This file is part of TATA-Sandbox.
# 
# TATA-Sandbox is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# TATA-Sandbox is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with TATA-Sandbox.  If not, see <http://www.gnu.org/licenses/>.
########################################################################


########################################################################
#
# Name       :: data.primary.py
# Created By :: Cory James Tobin < ctobin@caltech.edu >
# Created On :: Oct 11, 2007
# Purpose    :: Provide classes for various types of primary date types.
#               The only types currently are DNA, RNA, and protein which
#               all inherit the Sequence class.
#
########################################################################


import re
import types


class Sequence():
	"""
	"""
	
	##################
	#   Attributes   #
	##################
	
	# A list of all the possible characters for this type of seq
	_alphabet = []
	
	# Does this sequence have a compementary sequence? T/F
	_complementary = False
	
	# Map characters in the sequence to its complement character
	# Keys are primary character, values are complement characters
	_complementMap = {}
	
	# Strings to appear at the ends of the sequence (ie. 5' 3')
	_leftIdentifier = ""
	_rightIdentifier = ""
	
	# Complement end identifiers (ie. 3' 5')
	_compLeftIdentifier = ""
	_compRightIdentifier = ""
	
	# The primary sequence
	_sequence = ""
	
	
	
	
	####################
	#   Constructors   #
	####################
	
	def __init__(self):
		"""
		"""
		pass
	
	
	#################
	#   Accessors   #
	#################
	
	def getAlphabet(self):
		"""
		Returns a list of valid characters for this type of sequence.
		"""
		return list(self._alphabet)
	
	def isComplementary(self):
		"""
		True/False Is this type of sequence complementary?
		"""
		return bool(self._complementary)
	
	def getComplementMap(self):
		"""
		Returns the complement-map as a dictionary. ie.{A->T, G->C}
		"""
		return dict(self._complementMap)
	
	def getLeftIdentifier(self):
		"""
		Returns the string used to identify the left side of the sequence
		(ie. 5' or 3')
		"""
		return str(self._leftIdentifier)
	
	def getRightIdentifier(self):
		"""
		Returns the string used to identify the right side of the sequence
		(ie. 5' or 3')
		"""
		return str(self._rightIdentifier)
	
	def getCompLeftIdentifier(self):
		"""
		Returns the string used to identify the left side of the complement
		sequence (ie. 5' or 3')
		"""
		return str(self._compLeftIdentifier)
	
	def getCompRightIdentifier(self):
		"""
		Returns the string used to identify the right side of the complement
		sequence (ie. 5' or 3')
		"""
		return str(self._compRightIdentifier)
	
	def getSequence(self, *args):
		"""
		Returns the whole primary sequence of any protion of the sequence.
		Use arguments like brackets on a list [begin:end:skip]. You can have
		from 0-3 arguments, all ints.
		"""
		if len(args) == 0:
			return self._sequence
		elif len(args) == 1:
			return self._sequence[ int(args[0]) ]
		elif len(args) == 2:
			return self._sequence[ int(args[0]) : int(args[1]) ]
		elif len(args) == 3:
			return self._sequence[ int(args[0]) : int(args[1]) : int(args[2]) ]
		else:
			raise TypeError, "getSequence() takes 0-3 arguments, %s provided." % len(args)
	
	
	################
	#   Mutators   #
	################
	
	# Build reg-exp to find invalid characters
	def _validateSequence(self, seq):
		"""
		Check to make sure there are no invalid characters in the sequence
		Pass in a string to the 'seq' argument
		Return True/False, T means the seq contained no invalid chars
		"""
		# Find everything except the letters defined in alphabet
		expression = "[^%s]" % "".join(self._alphabet)
		pattern = re.compile(expression)
		
		if pattern.search(seq) == None:
			return True
		else:
			return False
		
	def setSequence(self, seq):
		"""
		Change the primary sequence to a new one.
		Pass a string containing only valid characters to the 'seq' argument.
		All characters will be stripped and uper-cased.
		"""
		#Clean up the incoming sequence
		cleanSeq = seq.upper().strip()
		
		# Validate the new sequence
		if not self._validateSequence(cleanSeq):
			raise ValueError, "Invalid character(s) in sequence"
		
		# Set the new sequence
		self._sequence = cleanSeq
	
	def insertSequence(self, seq, afterPositionNumber):
		"""
		Insert a new sequence in between 2 monomers of the existing sequence
		Pass a string containing only valid characters to the 'seq' argument.
		Pass an integer to the 'afterPositionNum' of the position you want to insert after (begin w/ zero)
		All characters will be stripped and uper-cased.
		"""
		#Clean up the incoming sequence
		cleanSeq = seq.upper().strip()
		
		# Validate position integer
		if type(afterPositionNumber) != types.IntType:
			raise ValueError, "Required an int, got a %s" % type(afterPositionNumber)
		if afterPositionNumber < 0 or afterPositionNumber > (len(self._sequence)-2):
			raise ValueError, "afterPositionNumber should be >= 0 and <= %s" % (len(self._sequence)-2)
		
		# Validate the new sequence
		if not self._validateSequence(cleanSeq):
			raise ValueError, "Invalid character(s) in sequence"
		
		# Set the new sequence
		newSeq = self._sequence[:afterPositionNumber+1] + cleanSeq + self._sequence[afterPositionNumber+1]
	
	def appendSequence(self, seq):
		"""
		Add a new sequence to the end of the current sequence.
		Pass a string containing only valid chars to the 'seq' argument.
		All characters will be stripped and uper-cased.
		"""
		#Clean up the incoming sequence
		cleanSeq = seq.upper().strip()
		
		# Validate the new sequence
		if not self._validateSequence(cleanSeq):
			raise ValueError, "Invalid character(s) in sequence"
		
		# Set the new sequence
		self._sequence = self._sequence + cleanSeq
	
	def prependSequence(self, seq):
		"""
		Add a new sequence to the beginning of the current sequence.
		Pass a string containing only valid chars to the 'seq' argument.
		All characters will be stripped and uper-cased.
		"""
		#Clean up the incoming sequence
		cleanSeq = seq.upper().strip()
		
		# Validate the new sequence
		if not self._validateSequence(cleanSeq):
			raise ValueError, "Invalid character(s) in sequence"
		
		# Set the new sequence
		self._sequence = cleanSeq + self._sequence
	


class DNA( Sequence ):
	"""
	"""
	
	##################
	#   Attributes   #
	##################
	
	# A list of all the possible deoxyribonucleic acids (or commonly used nucleotides anyways)
	_alphabet = ['A','T','G','C']
	
	# DNA is complementary
	_complementary = True
	
	# Map characters in the sequence to its complement character
	# Keys are primary character, values are complement characters
	_complementMap = {'A':'T',
			  'T':'A',
			  'G':'C',
			  'C':'G' }
	
	# Strings to appear at the ends of the sequence (ie. 5' 3')
	_leftIdentifier = "5'"
	_rightIdentifier = "3'"
	
	# Compement end identifiers (ie. 3' 5')
	_compLeftIdentifier = "3'"
	_compRightIdentifier = "5'"
	
	
	####################
	#   Constructors   #
	####################
	
	def __init__(self):
		"""
		"""
		pass
	
	
	################
	#   Mutators   #
	################
	
	
	#################
	#   Accessors   #
	#################
	


class RNA( Sequence ):
	"""
	"""

	##################
	#   Attributes   #
	##################
	
	# A list of all the possible ribonucleic acids (or commonly used nucleotides anyways)
	_alphabet = ['A','U','G','C']
	
	# RNA is complementary
	_complementary = True
	
	# Map characters in the sequence to its complement character
	# Keys are primary character, values are complement characters
	_complementMap = {'A':'U',
			  'U':'A',
			  'G':'C',
			  'C':'G' }
	
	# Strings to appear at the ends of the sequence (ie. 5' 3')
	_leftIdentifier = "5'"
	_rightIdentifier = "3'"
	
	# Compement end identifiers (ie. 3' 5')
	_compLeftIdentifier = "3'"
	_compRightIdentifier = "5'"
	
	
	####################
	#   Constructors   #
	####################
	
	def __init__(self):
		"""
		"""
		pass
	
	
	################
	#   Mutators   #
	################
	
	
	#################
	#   Accessors   #
	#################
	



class AminoAcid( Sequence ):
	"""
	"""
	
	##################
	#   Attributes   #
	##################
	
	# A list of the 20 amino acid 1-letter codes
	# I got the list from here <http://www.dna.affrc.go.jp/misc/MPsrch/InfoIUPAC.html>
	_alphabet = ['A','R','N','D','C','Q','E','G','H','I','L','K','M','F','P','S','T', \
		     'W','Y','V','B','Z','X']
	
	# Proteins are not complementary
	_complementary = False
	
	# Strings to appear at the ends of the sequence (ie. 5' 3')
	_leftIdentifier = "N"
	_rightIdentifier = "P"
	
	
	####################
	#   Constructors   #
	####################
	
	def __init__(self):
		"""
		"""
		pass
	
	
	################
	#   Mutators   #
	################
	
	
	#################
	#   Accessors   #
	#################
	

